from io import BytesIO
from typing import Annotated, Dict, List, Literal, Optional
from urllib import parse

import openpyxl
from fastapi import APIRouter, Body, Depends, File, Path, Query, Request, UploadFile
from fastapi.responses import StreamingResponse

from app.api.deps import (
    CurrentUserDep,
    DeptServiceDep,
    RoleServiceDep,
    SettingsDep,
    UserServiceDep,
    require_permission,
)
from app.core import settings
from app.enums.gender_label import GenderEnum
from app.enums.log_module import LogModuleEnum
from app.enums.result_code import ResultCode
from app.exceptions.base import AppException
from app.exceptions.user import UserDuplicateException, UserNotFoundException
from app.models.system import SysUser
from app.schemas.base import Pagination, Response
from app.schemas.option import StringOption
from app.schemas.user import (
    CurrentUserDTO,
    ExcelResult,
    PasswordUpdateForm,
    UserForm,
    UserPageQuery,
    UserPageView,
    UserProfileForm,
    UserProfileVO,
    UserUpdate,
)
from app.utils.log_api_call import log
from app.utils.pwd_utils import get_password_hash, verify_password
from app.utils.str_utils import generate_random_string

router = APIRouter(prefix=f"{settings.API_V1_STR}/users", tags=["02.用户接口"])


@router.get(
    "/me",
    response_model=Response[CurrentUserDTO],
    summary="获取当前登录用户信息",
    description="获取当前认证用户的详细信息",
)
async def get_current_user(current_user: CurrentUserDep) -> Response[CurrentUserDTO]:
    """
    获取当前登录用户信息
    """
    try:
        roles = [role.code for role in current_user.roles]
        permissions = [menu.perm for role in current_user.roles for menu in role.menus if menu.perm]

        return Response.success(
            data=CurrentUserDTO(
                user_id=current_user.id,
                username=current_user.username,
                nickname=current_user.nickname,
                avatar=current_user.avatar,
                roles=roles,
                perms=permissions,
            )
        )
    except Exception as e:
        raise AppException(message="获取当前登录用户信息失败") from e


@router.get(
    "/page", response_model=Response[Pagination[UserPageView]], summary="用户分页列表", description="分页查询用户列表"
)
@log(content="用户分页列表", module=LogModuleEnum.USER.label)
async def get_user_page(
    request: Request,
    user_service: UserServiceDep,
    current_user: CurrentUserDep,
    page_num: int = Query(1, alias="pageNum", description="页码"),
    page_size: int = Query(10, alias="pageSize", description="每页记录数"),
    keywords: str = Query(None, max_length=128, description="关键字(用户名/昵称/手机号)"),
    status: int = Query(None, description="用户状态(1-正常 0-禁用)"),
    dept_id: int = Query(None, alias="deptId", description="部门ID"),
    role_ids: List[int] = Query(None, alias="roleIds", description="角色ID"),
    field: Optional[Literal["create_time", "update_time"]] = Query(None, description="排序字段"),
    direction: Literal["asc", "desc"] = Query("asc", description="排序方式"),
) -> Response[Pagination[UserPageView]]:
    """
    用户分页列表
    """
    try:
        roles = [role.code for role in current_user.roles]
        is_root = settings.ADMIN_ROLE_CODE in roles

        # 构建查询参数：将前端传递的查询参数转换为数据库查询参数
        query_params = UserPageQuery(
            page_num=page_num,
            page_size=page_size,
            keywords=keywords,
            status=status,
            dept_id=dept_id,
            role_ids=role_ids,
            field=field,
            direction=direction,
            is_root=is_root,
        )
        reulst = await user_service.list_user_page(query_params)
        return Response.success(data=reulst)
    except Exception as e:
        raise AppException(message="获取用户分页列表失败") from e


@router.post("", response_model=Response[Dict], summary="新增用户", description="创建新用户，需 sys:user:add 权限")
@log(content="新增用户", module=LogModuleEnum.USER.label)
async def save_user(
    request: Request,
    user_service: UserServiceDep,
    current_user: Annotated[SysUser, Depends(require_permission("sys:user:add"))],
    user_form: UserForm = Body(..., description="用户表单数据"),
) -> Response[Dict]:
    """
    新增用户
    """
    try:
        user = await user_service.get_user_by_username(user_form.username)
        if user:
            raise UserDuplicateException()

        pwd = generate_random_string()

        user_dict = user_form.model_dump(exclude_unset=True)

        user_dict["password"] = get_password_hash(pwd)

        db_obj = SysUser(**user_dict)
        db_obj.create_by = current_user.id
        user = await user_service.save_user_with_roles(db_obj, user_form.role_ids)

        return Response.success(data={"userId": user.id, "password": pwd})
    except Exception as e:
        raise AppException(message="新增用户失败") from e


@router.get(
    "/{user_id}/form",
    response_model=Response[UserForm],
    summary="获取用户表单数据",
    description="根据用户ID获取编辑表单数据，需 sys:user:edit 权限",
)
async def get_user_form(
    user_service: UserServiceDep,
    current_user: Annotated[SysUser, Depends(require_permission("sys:user:edit"))],
    user_id: int = Path(..., description="用户ID"),
) -> Response[UserForm]:
    """
    获取用户表单数据
    """
    try:
        user = await user_service.get_user_byid(user_id)
        if not user:
            raise UserNotFoundException()
        # 转换为表单对象：将数据库模型转换为用户表单对象
        user_form = UserForm(
            id=user.id,
            username=user.username,
            nickname=user.nickname,
            mobile=user.mobile,
            gender=user.gender,
            avatar=user.avatar,
            status=user.status,
            email=user.email,
            dept_id=user.dept.id if user.dept else None,
            role_ids=[role.id for role in user.roles] if user.roles else None,
            open_id=user.openid,
            op_id=current_user.id,
        )
        return Response.success(data=user_form)
    except Exception as e:
        raise AppException(message="获取用户表单数据失败") from e


@router.put(
    "/{userId}",
    response_model=Response[None],
    summary="修改用户",
    description="更新用户基本信息，需 sys:user:edit 权限",
)
@log(content="修改用户", module=LogModuleEnum.USER.label)
async def update_user(
    request: Request,
    user_service: UserServiceDep,
    current_user: Annotated[SysUser, Depends(require_permission("sys:user:edit"))],
    userId: int = Path(..., description="用户ID"),
    user_form: UserForm = Body(..., description="用户表单数据"),
) -> Response[None]:
    """
    修改用户
    """
    try:
        user_form.op_id = current_user.id
        user_form.id = userId
        await user_service.update_user_with_roles(user_form)

        return Response.success()
    except Exception as e:
        raise AppException(message="修改用户失败") from e


@router.delete(
    "/{ids}",
    response_model=Response[None],
    summary="删除用户",
    description="批量删除用户，多个ID用英文逗号分隔，需 sys:user:delete 权限",
)
@log(content="删除用户", module=LogModuleEnum.USER.label)
async def delete_users(
    request: Request,
    user_service: UserServiceDep,
    current_user: Annotated[SysUser, Depends(require_permission("sys:user:edit"))],
    ids: str = Path(..., description="用户ID，多个以英文逗号(,)分割"),
) -> Response[None]:
    """
    删除用户
    """
    try:
        user_ids = [int(id) for id in ids.split(",")]
        op_id = current_user.id
        flag = await user_service.delete_users(op_id, user_ids)
        if flag:
            return Response.success()
        else:
            return Response.failed()
    except Exception as e:
        raise AppException(message="删除用户失败") from e


@router.patch(
    "/{user_id}/status",
    response_model=Response[None],
    summary="修改用户状态",
    description="启用或禁用用户，需 sys:user:edit 权限",
)
@log(content="修改用户状态", module=LogModuleEnum.USER.label)
async def update_user_status(
    request: Request,
    user_service: UserServiceDep,
    current_user: Annotated[SysUser, Depends(require_permission("sys:user:edit"))],
    status: Literal[0, 1] = Query(..., description="用户状态(1:启用;0:禁用)"),
    user_id: int = Path(..., description="用户ID"),
) -> Response[None]:
    """
    修改用户状态
    """
    try:
        flag = await user_service.update_user_status(current_user.id, user_id, status)
        if flag:
            return Response.success()
        else:
            return Response.failed()
    except Exception as e:
        raise AppException(message="修改用户状态失败") from e


@router.get("/template", summary="用户导入模板下载", description="下载用户批量导入的 Excel 模板文件")
async def download_template(
    current_user: CurrentUserDep,
    settings: SettingsDep,
) -> StreamingResponse:
    """
    用户导入模板下载
    """
    # 读取模板文件
    try:
        with open(settings.TEMPLATES_DIR / "user_template.xlsx", "rb") as f:
            template_content = f.read()
        return StreamingResponse(
            content=BytesIO(template_content),
            media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
            headers={"Content-Disposition": "attachment; filename=user_template.xlsx"},
        )
    except Exception as e:
        raise AppException(message="下载模板文件失败") from e


@router.post(
    "/import",
    response_model=Response[ExcelResult],
    summary="导入用户",
    description="上传 Excel 文件批量导入用户，需 sys:user:import 权限",
)
@log(content="导入用户", module=LogModuleEnum.USER.label)
async def import_users(
    request: Request,
    user_service: UserServiceDep,
    dept_service: DeptServiceDep,
    role_service: RoleServiceDep,
    current_user: Annotated[SysUser, Depends(require_permission("sys:user:import"))],
    file: UploadFile = File(..., alias="file", description="上传的Excel文件"),
) -> Response[ExcelResult]:
    """
    导入用户
    """
    try:
        message_list = []
        valid_count = 0
        invalid_count = 0

        # 读取上传的Excel文件
        contents = await file.read()
        workbook = openpyxl.load_workbook(BytesIO(contents))
        worksheet = workbook.active
        # 遍历工作表中的行
        for row in worksheet.iter_rows(min_row=2, values_only=True):
            # 用户名	昵称	性别	手机号码	邮箱	角色	部门
            username, nickname, gender_label, mobile, email, role_codes, dept_code = row
            # 检查用户名是否已存在
            existing_user = await user_service.get_user_by_username(username)
            if existing_user:
                continue  # 跳过已存在的用户
            # 创建新用户
            pwd = generate_random_string()

            dept_id = None
            role_ids = None

            if dept_code:
                dept = await dept_service.get_dept_by_code(dept_code)
                if not dept:
                    message_list.append(f"用户 {username}，部门编码 {dept_code} 不存在")
                    invalid_count += 1
                    continue  # 跳过不存在的部门
                dept_id = dept.id

            if gender_label:
                if gender_label not in GenderEnum.get_labels():
                    message_list.append(f"用户 {username}，性别 {gender_label} 不存在")
                    invalid_count += 1
                    continue  # 跳过不存在的性别

            if role_codes:
                role_code_list = role_codes.split(",")
                roles = await role_service.get_role_by_code(role_code_list)
                if not roles:
                    message_list.append(f"用户 {username}，角色编码 {role_codes} 不存在")
                    invalid_count += 1
                    continue  # 跳过不存在的角色
                if len(roles) != len(role_code_list):
                    for role_code in role_code_list:
                        if role_code not in [role.code for role in roles]:
                            message_list.append(f"用户 {username}，角色编码 {role_code} 不存在")

                role_ids = [role.id for role in roles]

            user_form = UserForm(
                username=username,
                nickname=nickname,
                mobile=mobile,
                password=pwd,
                gender=GenderEnum.from_label(gender_label).code,
                email=email,
                role_ids=role_ids,
                dept_id=dept_id,
            )

            db_obj = SysUser.model_validate(user_form, update={"password": get_password_hash(pwd)})
            await user_service.save_user_with_roles(db_obj, user_form.role_ids)

            valid_count += 1

        return Response.success(
            data=ExcelResult(
                code=ResultCode.SUCCESS.code if valid_count > 0 else ResultCode.SYSTEM_ERROR.code,
                invalid_count=invalid_count,
                valid_count=valid_count,
                messageList=message_list,
            ),
            message=f"导入完成，成功{valid_count}条，失败{invalid_count}条",
        )
    except Exception as e:
        raise AppException(message="导入用户失败") from e


@router.get("/export", summary="导出用户", description="根据查询条件导出用户列表为 Excel 文件，需 sys:user:export 权限")
@log(content="导出用户", module=LogModuleEnum.USER.label)
async def export_users(
    request: Request,
    current_user: Annotated[SysUser, Depends(require_permission("sys:user:export"))],
    user_service: UserServiceDep,
    page_num: int = Query(1, alias="pageNum", description="页码"),
    page_size: int = Query(10, alias="pageSize", description="每页记录数"),
    keywords: str = Query(None, max_length=128, description="关键字(用户名/昵称/手机号)"),
    status: int = Query(None, description="用户状态(1-正常 0-禁用)"),
    dept_id: int = Query(None, alias="deptId", description="部门ID"),
    role_ids: List[int] = Query(None, alias="roleIds", description="角色ID"),
    field: Literal["create_time", "update_time"] = Query("username", description="排序字段"),
    direction: Literal["asc", "desc"] = Query("asc", description="排序方式"),
) -> StreamingResponse:
    """
    导出用户
    """
    try:
        roles = [role.code for role in current_user.roles]
        is_root = settings.ADMIN_ROLE_CODE in roles

        # 构建查询参数：将前端传递的查询参数转换为数据库查询参数
        query_params = UserPageQuery(
            page_num=page_num,
            page_size=page_size,
            keywords=keywords,
            status=status,
            dept_id=dept_id,
            role_ids=role_ids,
            field=field,
            direction=direction,
            is_root=is_root,
        )
        reulst = await user_service.list_user_page(query_params)
        users = reulst.items

        # 创建Excel工作簿和工作表
        workbook = openpyxl.Workbook()
        worksheet = workbook.active
        worksheet.title = "用户列表"

        # 设置列标题
        headers = ["用户名", "用户昵称", "部门", "性别", "手机号码", "邮箱", "创建时间", "角色"]
        for col_num, header in enumerate(headers, 1):
            cell = worksheet.cell(row=1, column=col_num)
            cell.value = header

        # 填充数据
        for row_num, user in enumerate(users, 2):
            # 获取部门名称
            dept_name = user.dept.name if user.dept else ""

            # 获取性别文本
            gender_text = GenderEnum.from_code(user.gender).label if user.gender else ""

            # 格式化创建时间
            create_time_str = user.create_time.strftime("%Y/%m/%d %H:%M:%S") if user.create_time else ""

            # 获取角色名称
            role_names = ",".join([role.name for role in user.roles]) if user.roles else ""

            # 填充单元格
            worksheet.cell(row=row_num, column=1, value=user.username)
            worksheet.cell(row=row_num, column=2, value=user.nickname)
            worksheet.cell(row=row_num, column=3, value=dept_name)
            worksheet.cell(row=row_num, column=4, value=gender_text)
            worksheet.cell(row=row_num, column=5, value=user.mobile)
            worksheet.cell(row=row_num, column=6, value=user.email)
            worksheet.cell(row=row_num, column=7, value=create_time_str)
            worksheet.cell(row=row_num, column=8, value=role_names)

        # 调整列宽
        for col in worksheet.columns:
            max_length = 0
            column = col[0].column_letter
            for cell in col:
                if cell.value:
                    max_length = max(max_length, len(str(cell.value)))
            adjusted_width = max_length + 2
            worksheet.column_dimensions[column].width = adjusted_width

        # 将Excel写入内存
        excel_file = BytesIO()
        workbook.save(excel_file)
        excel_file.seek(0)

        filename = "用户列表.xlsx"
        # 处理文件名中的特殊字符，例如空格或中文
        encoded_filename = parse.quote(filename, safe="")
        headers = {"Content-Disposition": f"attachment; filename*=utf-8''{encoded_filename}"}

        return StreamingResponse(
            content=excel_file,
            media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
            headers=headers,
        )
    except Exception as e:
        raise AppException(message="导出用户失败") from e


@router.get(
    "/profile",
    response_model=Response[UserProfileVO],
    summary="获取个人中心用户信息",
    description="获取当前用户在个人中心展示的资料信息",
)
async def get_user_profile(current_user: CurrentUserDep) -> Response[UserProfileVO]:
    """
    获取个人中心用户信息
    """
    try:
        result = UserProfileVO.model_validate(current_user)
        result.dept_name = current_user.dept.name if current_user.dept else ""
        result.role_names = ",".join([role.name for role in current_user.roles]) if current_user.roles else ""
        return Response.success(data=result)
    except Exception as e:
        raise AppException(message="获取个人中心用户信息失败") from e


@router.put(
    "/profile",
    response_model=Response[None],
    summary="个人中心修改用户信息",
    description="当前用户更新个人资料（不含密码、手机号、邮箱等敏感信息）",
)
@log(content="个人中心修改用户信息", module=LogModuleEnum.USER.label)
async def update_user_profile(
    request: Request,
    user_service: UserServiceDep,
    current_user: CurrentUserDep,
    form_data: UserProfileForm = Body(..., description="个人资料更新表单"),
) -> Response[None]:
    """
    个人中心修改用户信息
    """
    try:
        user_update = UserUpdate.model_validate(form_data)
        await user_service.update_user(current_user.id, user_update)
        return Response.success()
    except Exception as e:
        raise AppException(message="个人中心修改用户信息失败") from e


@router.put(
    "/{user_id}/password/reset",
    response_model=Response[None],
    summary="重置用户密码",
    description="管理员重置指定用户的密码，需 sys:user:reset-password 权限",
)
@log(content="重置用户密码", module=LogModuleEnum.USER.label)
async def reset_password(
    request: Request,
    current_user: Annotated[SysUser, Depends(require_permission("sys:user:reset-password"))],
    user_service: UserServiceDep,
    user_id: int = Path(..., description="用户ID"),
    password: str = Query(..., description="新密码"),
) -> Response[None]:
    """
    重置用户密码
    """
    try:
        user_update = UserUpdate(password=get_password_hash(password))
        await user_service.update_user(user_id, user_update)
        return Response.success()
    except Exception as e:
        raise AppException(message="重置用户密码失败") from e


@router.put("/password", response_model=Response[None], summary="修改密码", description="当前用户修改自己的登录密码")
@log(content="修改密码", module=LogModuleEnum.USER.label)
async def change_password(
    request: Request,
    current_user: CurrentUserDep,
    user_service: UserServiceDep,
    data: PasswordUpdateForm = Body(..., description="密码更新表单"),
) -> Response[None]:
    """
    修改密码
    """
    try:
        if not verify_password(data.old_password, current_user.password):
            raise AppException(message="原密码错误")

        if get_password_hash(data.new_password) == current_user.password:
            raise AppException(message="新密码不能与原密码相同")

        if data.new_password != data.confirm_password:
            raise AppException(message="两次输入的新密码不一致")

        user_update = UserUpdate(password=get_password_hash(data.new_password))
        await user_service.update_user(current_user.id, user_update)
        return Response.success()
    except Exception as e:
        raise AppException(message="修改密码失败") from e


@router.post(
    "/mobile/code",
    response_model=Response[None],
    summary="发送短信验证码（绑定或更换手机号）",
    description="向指定手机号发送验证码用于绑定或更换",
)
async def send_mobile_code(
    current_user: CurrentUserDep,
    mobile: str = Query(..., description="手机号码", required=True),
) -> Response[None]:
    """
    发送短信验证码（绑定或更换手机号）
    """
    pass


@router.put(
    "/mobile",
    response_model=Response[None],
    summary="绑定或更换手机号",
    description="当前用户绑定或更换手机号（需验证码验证）",
)
async def bind_or_change_mobile(
    current_user: CurrentUserDep,
    mobile: str = Query(..., description="手机号码", required=True),
    code: str = Query(..., description="验证码", required=True),
) -> Response[None]:
    """
    绑定或更换手机号
    """
    pass


@router.post(
    "/email/code",
    response_model=Response[None],
    summary="发送邮箱验证码（绑定或更换邮箱）",
    description="向指定邮箱发送验证码用于绑定或更换",
)
async def send_email_code(
    current_user: CurrentUserDep,
    email: str = Query(..., description="邮箱地址", required=True),
) -> Response[None]:
    """
    发送邮箱验证码（绑定或更换邮箱）
    """
    pass


@router.put(
    "/email",
    response_model=Response[None],
    summary="绑定或更换邮箱",
    description="当前用户绑定或更换邮箱（需验证码验证）",
)
async def bind_or_change_email(
    current_user: CurrentUserDep,
    email: str = Query(..., description="邮箱地址", required=True),
    code: str = Query(..., description="验证码", required=True),
) -> Response[None]:
    """
    绑定或更换邮箱
    """
    pass


@router.get(
    "/options",
    response_model=Response[List[StringOption]],
    summary="获取用户下拉选项",
    description="获取可用于下拉选择的用户列表（如分配角色时选择用户）",
)
async def list_user_options(
    user_service: UserServiceDep,
    current_user: CurrentUserDep,
) -> Response[List[StringOption]]:
    """
    获取用户下拉选项
    """
    try:
        roles = [role.code for role in current_user.roles]
        is_root = settings.ADMIN_ROLE_CODE in roles
        _options = await user_service.list_user_options(is_root)
        return Response.success(data=_options)
    except Exception as e:
        raise AppException(message="获取用户下拉选项失败") from e
